package com.google.code.yargon.ui;

import org.apache.log4j.Logger;
import org.lwjgl.input.Keyboard;

import com.google.code.yargon.Yargon;
import com.google.code.yargon.level.Direction;
import com.google.code.yargon.level.LevelArea;
import com.google.code.yargon.level.LevelManager;
import com.google.code.yargon.ui.KeyMap.Input;
import com.google.code.yargon.util.LogUtil;
import com.google.code.yargon.db.Dao;
import com.google.code.yargon.engine.Player;

import static com.google.code.yargon.util.Constants.*;

public class InputHandler {
	
	private Logger log = LogUtil.getInstance();
		
	// If we are waiting for specific input
	private Input waitInput;
	private boolean waitFlag = false;
	
	private static KeyMap keyMap  = new KeyMap();
	
	// Singleton instance
	private static InputHandler instance = null;
	
	// Meta key states
	private enum metaKeys {
		none,
		shift,
		ctrl
	}
	
	/**
	 * Private constructor - singleton class
	 */
	private InputHandler() {
		
	}
	
	/**
	 * Get the singleton instance 
	 * 
	 * @return
	 */
	public static InputHandler getInstance() {
		if(null == instance) {
			instance = new InputHandler();
		}
		return instance;
	}
	
	/**
	 * Handle keyboard events
	 * 
	 * Returns true if the input events require level re-rendering
	 * 
	 */
	public boolean handleInput() {
					
		int keyInput[] = getKeyValue();
		int keyValue = keyInput[0];
		int plainValue = 0;
		int metaKey = keyInput[1];
		
		boolean turnChange = false;
		
		if(keyValue != 0) {
			
			Input input;
			
			// Waiting for further input? If so, keep the current action
			if(waitFlag) {
				input = waitInput;
			} 
			// Or else read new action
			else {
				input = keyMap.getInput(keyValue);
				
			}
			
			// We need to manipulate these according to inputs
			Player pc = Yargon.getInstance().getPlayerCharacter();
			LevelArea level = LevelManager.getInstance().get(0);
			
			switch(input) {
	
			case INPUT_UP:
				pc.moveTo(new Direction(NORTH), level);
				turnChange = true;
				break;
	
			case INPUT_UPRIGHT:
				pc.moveTo(new Direction(NORTH_EAST), level);
				turnChange = true;
				break;
	
			case INPUT_RIGHT:
				pc.moveTo(new Direction(EAST), level);
				turnChange = true;
				break;
	
			case INPUT_DOWNRIGHT:
				pc.moveTo(new Direction(SOUTH_EAST), level);
				turnChange = true;
				break;
	
			case INPUT_DOWN:
				pc.moveTo(new Direction(SOUTH), level);
				turnChange = true;
				break;
	
			case INPUT_DOWNLEFT:
				pc.moveTo(new Direction(SOUTH_WEST), level);
				turnChange = true;
				break;
	
			case INPUT_LEFT:
				pc.moveTo(new Direction(WEST), level);
				turnChange = true;
				break;
	
			case INPUT_UPLEFT:
				pc.moveTo(new Direction(NORTH_WEST), level);
				turnChange = true;
				break;
				
			case INPUT_USEDOOR:
				
				// Wait flag not set yet, ask for direction
				if(! waitFlag) {
					waitFlag = true;
					waitInput = Input.INPUT_USEDOOR;
					RenderEngine.getInstance().message("Use a door which way? (press a direction key)");
				} 
				// Wait flag set, get the direction and use the door
				else {	
					switch(keyMap.getInput(keyValue)) {
					case INPUT_UP: 			pc.useDoor(new Direction(NORTH), level); 		break;
					case INPUT_UPRIGHT: 	pc.useDoor(new Direction(NORTH_EAST), level);	break;
					case INPUT_RIGHT: 		pc.useDoor(new Direction(EAST), level); 		break;
					case INPUT_DOWNRIGHT: 	pc.useDoor(new Direction(SOUTH_EAST), level); 	break;
					case INPUT_DOWN: 		pc.useDoor(new Direction(SOUTH), level);		break;
					case INPUT_DOWNLEFT: 	pc.useDoor(new Direction(SOUTH_WEST), level);	break;
					case INPUT_LEFT: 		pc.useDoor(new Direction(WEST), level);			break;
					case INPUT_UPLEFT: 		pc.useDoor(new Direction(NORTH_WEST), level);	break;
					case INPUT_WAIT:		pc.useDoor(null,  level); 						break;
					default:				RenderEngine.getInstance().message("That's a strange direction!"); break;
					}
					waitFlag = false;
					turnChange = true;
				}
				break;
	
			case INPUT_WAIT:
				RenderEngine.getInstance().message("You sit down and wait");
				turnChange = true;
				break;
				
			case INPUT_SAVE:
				RenderEngine.getInstance().message("Saving game...");
				//Dao.getInstance().saveLevel(level);
				RenderEngine.getInstance().message("Done");
				break;
				
			case INPUT_LOAD:
				RenderEngine.getInstance().message("Loading game...");
				LevelManager.getInstance().clear();
				//LevelManager.getInstance().add(Dao.getInstance().loadLevel());
				RenderEngine.getInstance().message("Done");
				break;
				
			case INPUT_GODMODE:
				if(Yargon.getInstance().getDeveloperMode() == false) {
					Yargon.getInstance().setDeveloperMode(true);
					level.setRefresh(true);
					RenderEngine.getInstance().message("Developer mode ON"); 
					
				} else {
					Yargon.getInstance().setDeveloperMode(false);
					level.setRefresh(true);
					RenderEngine.getInstance().message("Developer mode OFF"); 
				}
				break;
				
			case INPUT_UNDEFINED:
			default:
				switch(metaKey) {
				case 0: log.debug("Unbound key "+keyValue+" ("+Keyboard.getKeyName(keyValue)+")"); break;
				case 1: plainValue = keyValue - 1000; log.debug("Unbound key "+keyValue+" ("+metaKeys.values()[metaKey]+"+"+Keyboard.getKeyName(plainValue)+")"); break;
				case 2: plainValue = keyValue - 2000; log.debug("Unbound key "+keyValue+" ("+metaKeys.values()[metaKey]+"+"+Keyboard.getKeyName(plainValue)+")"); break;
				}
				break;
			}
		} 
		
		return turnChange;
	}
		
	/**
	 * Return key value
	 * 
	 * @return
	 */
	private int[] getKeyValue() {
		
		int keyValue = 0;
		int metaKey = metaKeys.none.ordinal();
		
		while(Keyboard.next()) {
			if(Keyboard.getEventKeyState()) {
				keyValue = Keyboard.getEventKey();
				
				// If shift was down, increase key value by 1000
				if(Keyboard.isKeyDown(Keyboard.KEY_LSHIFT) || Keyboard.isKeyDown(Keyboard.KEY_RSHIFT)) {
					metaKey = metaKeys.shift.ordinal();
					keyValue += 1000;
				}
				
				// If ctrl was down, increase key value by 2000
				if(Keyboard.isKeyDown(Keyboard.KEY_LCONTROL) || Keyboard.isKeyDown(Keyboard.KEY_RCONTROL)) {
					metaKey = metaKeys.ctrl.ordinal();
					keyValue += 2000;
				}				
			}
		} 	
		
		// Filter out plain meta-presses
		if(keyValue == (Keyboard.KEY_LSHIFT + 1000) || keyValue == (Keyboard.KEY_RSHIFT + 1000) || 
		   keyValue == (Keyboard.KEY_LCONTROL + 2000) || keyValue == (Keyboard.KEY_RCONTROL + 2000)) {
			keyValue = 0;
		}
		int ret[] = {keyValue, metaKey};
		return ret;
	}
}
